/*******************************************************************************
 * Copyright 2007, 2009 InnovaSoft SA http://www.innovasoft.biz/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package biz.innovasoft.ndb.query;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.lucene.search.SortField;
import org.prevayler.Prevayler;

import biz.innovasoft.ndb.FindHelper;
import biz.innovasoft.ndb.RepositoryException;
import biz.innovasoft.ndb.index.SyntaxErrorException;

/**
 * The default implementation of QueryCriteria.
 * 
 * @author Diego Miranda
 *
 */
public class QueryCriteriaImpl implements QueryCriteria {
	
	private static final long serialVersionUID = 6128354043445104702L;
	
	private Class<?> clazz;
	private List<Expression> expressions = new ArrayList<Expression>();
	private List<SortField> orderByFields = new ArrayList<SortField>();
	private int page = 0;
	private int maxResults = 20;
	private boolean checkForIndex = false;
	private boolean isStricted = false;
	private transient FindHelper finder;
	private transient Prevayler prevayler;
	

	public QueryCriteriaImpl(Class<?> clazz) {
		this.clazz = clazz;
	}
	
	public QueryCriteriaImpl (Class<?> clazz, FindHelper finder, boolean stricted) {
		this.clazz = clazz;
		this.finder = finder;
		this.isStricted = stricted;
	}
	
	public QueryCriteriaImpl (Class<?> clazz, Prevayler prevayler, boolean stricted) {
		this.clazz = clazz;
		this.isStricted = stricted;
		this.prevayler = prevayler;
	}

	public QueryCriteria where(Expression expression) {
		expressions.add(expression);
		return this;
	}
	
	private void setWhere (Where where) {
		expressions = new ArrayList<Expression>();
		expressions.add(where.getExpression());
	}

	public QueryCriteria orderBy(String field) {
		orderBy(field, false);
		return this;
	}
	
	public QueryCriteria orderBy(String field, boolean reverse) {
		orderByFields.add(new SortField(field, reverse));
		return this;
	}

	public QueryCriteria page (int page) {
		if (page > 0) {
			this.page = page;	
		}
		return this;
	}
	
	public QueryCriteria maxResults (int maxResults) {
		if (maxResults > 0) {
			this.maxResults = maxResults;
		}
		return this;
	}
	
	public int getPage () {
		return this.page;
	}
	
	public int getMaxResults () {
		return this.maxResults;
	}
	
	public Class<?> getCriteriaClass() {
		return clazz;
	}

	public SortField[] getSortFields() {
		return (SortField[]) orderByFields.toArray(new SortField[0]);
	}

	public List<Expression> getExpressions() {
		return expressions;
	}
	
	public boolean isStricted () {
		return isStricted;
	}
	
	public boolean isCheckForIndex() {
		return checkForIndex;
	}

	public QueryCriteria checkForIndex(boolean checkForIndex) {
		this.checkForIndex = checkForIndex;
		return this;
	}

	public String toString() {
		StringBuffer buffer = new StringBuffer();
		if (!isStricted) {
			buffer.append("class:" + clazz.getCanonicalName());
			if (expressions.size() > 0) {
				buffer.append(" AND ");
				buffer.append(getExpressionString());
			}
		} else {
			buffer.append (getExpressionString());
			if (buffer.toString().equals("")) {
				throw new SyntaxErrorException ("A stricted QueryCriteria must have expressions to query.");
			}
		}
		return buffer.toString();
	}

	public String getExpressionString() {
		StringBuffer buffer = new StringBuffer();
		for (Expression exp : expressions) {
			buffer.append(exp.toString());
		}
		return buffer.toString();
	}
	
	public boolean equals(Object obj) {
		return EqualsBuilder.reflectionEquals(this, obj);
	}

	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this);
	}

	private List<String> getExpressionAttribute(Expression expr) {
		List<String> attributes = new ArrayList<String>();

		if (expr instanceof MultinaryExpression) {
			return getExpressionAttributes(((MultinaryExpression) expr).expressions);
		} else {
			if (expr.operator.equals(Operator.AND)
					|| expr.operator.equals(Operator.OR)) {
				attributes.addAll(getExpressionAttribute(expr.right));
				if (expr.left != null) {
					attributes.addAll(getExpressionAttribute(expr.left));
				}

			} else {
				attributes.add((String) expr.attr);
			}
		}
		return attributes;
	}

	private List<String> getExpressionAttributes(
			List<Expression> expressions) {
		List<String> attributes = new ArrayList<String>();
		for (Expression expr : expressions) {
			attributes.addAll(getExpressionAttribute(expr));
		}
		return attributes;
	}
	
	public String[] getExpressionAttributesAsArray () {
		List<String> attr = getExpressionAttributes (expressions);
		return (String[])attr.toArray(new String[0]);
	}
	
	public String getQueryCacheId () {
		String[] attrs = getExpressionAttributesAsArray();
		StringBuffer buff = new StringBuffer();
		buff.append(clazz.getCanonicalName());
		for (String str : attrs ) {
			buff.append(str);
		}
		UUID uuid = UUID.nameUUIDFromBytes(buff.toString().getBytes());
		return uuid.toString();
	}
	
	public Result find () {
		if (prevayler != null) {
			try {
				return (Result)prevayler.execute(new FindQueryTransaction (this));
			} catch (Exception e) {
				throw new RepositoryException ("Error executing prevalent query: " + toString(), e);
			}
		}
		return finder.find(this, true);
	}
	
	public Result find (Where where) {
		setWhere ( where );
		return find();
	}
}
